package com.ruyuan.little.project.elasticsearch.biz.api.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ruyuan.little.project.common.dto.CommonResponse;
import com.ruyuan.little.project.common.dto.TableData;
import com.ruyuan.little.project.common.enums.ErrorCodeEnum;
import com.ruyuan.little.project.elasticsearch.biz.api.dto.OrderSearchDTO;
import com.ruyuan.little.project.elasticsearch.biz.api.entity.Order;
import com.ruyuan.little.project.elasticsearch.biz.api.enums.OrderStatusEnum;
import com.ruyuan.little.project.elasticsearch.biz.api.service.GoodsService;
import com.ruyuan.little.project.elasticsearch.biz.api.service.OrderService;
import com.ruyuan.little.project.elasticsearch.biz.common.constant.QueryFiledNameConstant;
import com.ruyuan.little.project.elasticsearch.biz.common.constant.StringPoolConstant;
import com.ruyuan.little.project.elasticsearch.biz.common.dao.ElasticsearchDao;
import com.ruyuan.little.project.elasticsearch.biz.common.utils.ElasticClientUtil;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.UUIDs;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

/**
 * @author <a href="mailto:little@163.com">little</a>
 * version: 1.0
 * Description:elasticsearch实战
 **/
@Service
public class OrderServiceImpl implements OrderService {

    /**
     * 日志组件
     */
    private final Logger LOGGER = LoggerFactory.getLogger(OrderServiceImpl.class);

    /**
     * 时间格式
     */
    private final DateTimeFormatter LOCAL_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(StringPoolConstant.FULL_DATE_TIME);

    /**
     * 订单index别名
     */
    @Value("${elasticsearch.orderIndexAlias}")
    private String orderIndexAlias;

    /**
     * 订单index别名
     */
    @Value("${elasticsearch.orderIndex}")
    private String orderIndex;

    /**
     * elasticsearch Dao 组件
     */
    @Autowired
    private ElasticsearchDao elasticsearchDao;

    /**
     * es rest client客户端
     */
    @Autowired
    private RestHighLevelClient restClient;

    /**
     * 商品服务组件
     */
    @Autowired
    private GoodsService goodsService;


    /**
     * 订单的mapping
     */
    @Value(value = "classpath:/mapping/orderIndex.json")
    private Resource orderIndexMappingResource;

    /**
     * 初始化订单的索引
     *
     * @throws IOException
     */
    @PostConstruct
    public void init() throws IOException {
        // 查询订单索引是否存在
        boolean storeIndexExistFlag = elasticsearchDao.indexExistFlag(orderIndex);
        if (!storeIndexExistFlag) {
            // 不存在
            // 创建订单索引
            Boolean created = elasticsearchDao.createIndex(orderIndex, orderIndexAlias, orderIndexMappingResource);
            LOGGER.info("create index {} ", created ? "success" : "fail");
        } else {
            LOGGER.info("index:{} already exist", orderIndex);
        }
    }

    @Override
    public CommonResponse createOrder(Order order) {
        // 构建新增请求
        IndexRequest indexRequest = new IndexRequest(orderIndexAlias);
        order.setId(UUIDs.base64UUID());
        order.setCreateTime(LocalDateTime.now().format(LOCAL_DATE_TIME_FORMATTER));
        order.setStatus(OrderStatusEnum.WAITING_FOR_PAY.getStatus());
        indexRequest.id(order.getId());
        indexRequest.source(JSONObject.toJSONString(order), XContentType.JSON);
        // 数据添加之后强制刷新
        indexRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);

        // 扣减库存
        CommonResponse commonResponse = goodsService.deductStock(order);
        // 如果扣减库存成功了，就创建订单，否则直接返回
        if (ErrorCodeEnum.SUCCESS.getCode().equals(commonResponse.getCode())){
            return elasticsearchDao.insert(indexRequest);
        }
        return commonResponse;
    }

    @Override
    public CommonResponse pay(Order order) {
        UpdateRequest updateRequest = new UpdateRequest(orderIndexAlias, order.getId());
        order.setPayTime(LocalDateTime.now().format(LOCAL_DATE_TIME_FORMATTER));
        order.setStatus(OrderStatusEnum.PAID.getStatus());
        updateRequest.doc(JSONObject.toJSONString(order), XContentType.JSON);
        // 数据修改之后强制刷新
        updateRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
        return elasticsearchDao.update(updateRequest);
    }

    @Override
    public CommonResponse cancel(Order order) {
        order = elasticsearchDao.getById(orderIndexAlias, order.getId(), Order.class);
        UpdateRequest updateRequest = new UpdateRequest(orderIndexAlias, order.getId());
        order.setCancelTime(LocalDateTime.now().format(LOCAL_DATE_TIME_FORMATTER));
        order.setStatus(OrderStatusEnum.CANCELED.getStatus());
        updateRequest.doc(JSONObject.toJSONString(order), XContentType.JSON);
        // 数据修改之后强制刷新
        updateRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);

        // 还原扣减的库存
        CommonResponse commonResponse = goodsService.unDeductStock(order);
        // 如果还原扣减的库存成功了，就修改订单状态，否则直接返回
        if (ErrorCodeEnum.SUCCESS.getCode().equals(commonResponse.getCode())){
            return elasticsearchDao.update(updateRequest);
        }
        return commonResponse;
    }


    @Override
    public CommonResponse getOrderById(String id) {
        Order order = elasticsearchDao.getById(orderIndexAlias, id, Order.class);
        order.setStatusName(OrderStatusEnum.getDescByStatus(order.getStatus()));
        return CommonResponse.success(order);
    }

    @Override
    public CommonResponse receive(Order order) {
        UpdateRequest updateRequest = new UpdateRequest(orderIndexAlias, order.getId());
        order.setReceiveTime(LocalDateTime.now().format(LOCAL_DATE_TIME_FORMATTER));
        order.setStatus(OrderStatusEnum.RECEIVED.getStatus());
        updateRequest.doc(JSONObject.toJSONString(order), XContentType.JSON);
        // 数据修改之后强制刷新
        updateRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
        return elasticsearchDao.update(updateRequest);
    }

    @Override
    public CommonResponse publishComment(Order order) {
        UpdateRequest updateRequest = new UpdateRequest(orderIndexAlias, order.getId());
        order.setFinishTime(LocalDateTime.now().format(LOCAL_DATE_TIME_FORMATTER));
        order.setStatus(OrderStatusEnum.FINISHED.getStatus());
        updateRequest.doc(JSONObject.toJSONString(order), XContentType.JSON);
        // 数据修改之后强制刷新
        updateRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
        return elasticsearchDao.update(updateRequest);
    }


    @Override
    public CommonResponse getOrderList(OrderSearchDTO orderSearchDTO) {
        LOGGER.info("查询订单列表");

        // 构建分页查询订单信息的资源构建器
        SearchSourceBuilder sourceBuilder = buildOrderListSource(orderSearchDTO);

        // 查询请求
        SearchRequest searchRequest = new SearchRequest(orderIndexAlias);
        searchRequest.source(sourceBuilder);

        try {
            LOGGER.info("ES请求参数：{}", searchRequest.source().toString());
            SearchResponse searchResponse = restClient.search(searchRequest, RequestOptions.DEFAULT);

            // 构建查询结果
            TableData<Order> tableData = buildOrderListResult(searchResponse, orderSearchDTO);
            return CommonResponse.success(tableData);
        } catch (IOException e) {
            LOGGER.error("查询订单列表失败", e);
        }
        return CommonResponse.fail();
    }

    /**
     * 构建分页查询订单信息的请求体
     *
     * @param orderSearchDTO    订单查询请求信息
     * @return                  结果
     */
    private SearchSourceBuilder buildOrderListSource(OrderSearchDTO orderSearchDTO) {
        // 构建查询
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
                .must(QueryBuilders.termQuery(QueryFiledNameConstant.PHONE_NUMBER, orderSearchDTO.getPhoneNumber()));
        // 订单状态不为空，根据状态查询
        if (StringUtils.hasLength(orderSearchDTO.getOrderStatus())) {
            boolQuery.must(QueryBuilders.termQuery(QueryFiledNameConstant.STATUS, orderSearchDTO.getOrderStatus()));
        }

        // 查询资源构建器
        SearchSourceBuilder sourceBuilder =
                ElasticClientUtil.builderPageSearchBuilder(orderSearchDTO.getPage(), orderSearchDTO.getSize());

        // 查询字段不为空，则添加商品名称或店铺名称查询
        if (!ObjectUtils.isEmpty(orderSearchDTO.getSearchContent())) {
            boolQuery.must(QueryBuilders.matchQuery(QueryFiledNameConstant.GOODS_NAME, orderSearchDTO.getSearchContent()));

            // 高亮显示
            ElasticClientUtil.sourceBuilderAddHighlight(sourceBuilder, QueryFiledNameConstant.GOODS_NAME);
        }
        sourceBuilder.query(boolQuery);

        // 排序构建器
        FieldSortBuilder sortBuilder = SortBuilders.fieldSort(QueryFiledNameConstant.CREATE_TIME).order(SortOrder.DESC);
        sourceBuilder.sort(sortBuilder);
        return sourceBuilder;
    }


    /**
     * 构建分页查询订单结果
     *
     * @param searchResponse es查询响应
     * @param orderSearchDTO 订单查询请求信息
     * @return 结果
     */
    private TableData<Order> buildOrderListResult(SearchResponse searchResponse, OrderSearchDTO orderSearchDTO) {
        TableData<Order> tableData = new TableData<>();
        SearchHits searchHits = searchResponse.getHits();
        long total = searchHits.getTotalHits().value;
        tableData.setTotal(total);
        List<Order> orderList = new ArrayList<>();
        for (SearchHit hit : searchHits) {
            Order order = JSON.parseObject(hit.getSourceAsString(), Order.class);
            // 转义状态枚举值
            order.setStatusName(OrderStatusEnum.getDescByStatus(order.getStatus()));

            // 构建高亮查询结果
            ElasticClientUtil.buildHighlightResult(hit, QueryFiledNameConstant.GOODS_NAME, orderSearchDTO.getSearchContent());
            orderList.add(order);
        }
        tableData.setRows(orderList);
        return tableData;
    }

}
